Sayed's Blog

Archimedes and logarithmic spirals

Posted September 30th, 2019

In his book "Climbing Mount Improbable", Richard Dawkins describes a museum containing every animal that has ever existed and every animal that could ever conceivably exist.

Every animal is located next to an animal that most closely resembles it. Every dimension in the museum corresponds to a dimension of variation within the animals - e.g sharpness of teeth is West to East and North to South is horn length.

Since there are more than 3 ways that animals can differ, this museum would have to have multiple dimensions. One way to visualise 4 dimensions is to imagine multiple 3 dimensional cubes, with animals occupying the same position relative to the cube are identical in the first 3 qualities but differ in the fourth (e.g coat hairiness). More dimensions can be visualised by making "families" of such cubes.

To illustrate his analogy more clearly, Dawkins draws attention to a subset of animals that can mostly be expressed with 3 variables - shells.

Many kinds of shells in nature can be modelled as logarithmic spirals.

In this post I will demonstrate how to draw different kinds of spirals using code. In a future post I will demonstrate how to model shells using spirals.

Archimedes Spiral

This is one of the most simple spirals to draw.

Let's start by creating 'archimedes.html'.

<!DOCTYPE html>
<html>
	<head>
		<title>Archimedes Spiral</title>
	</head>
	<body>
	<canvas width="720" height="480" id="canvas">

	</canvas>
	<script type="text/javascript">
		const canvas = document.getElementById("canvas");
		const ctx = canvas.getContext("2d");

	</script>

	<style type="text/css">
		canvas {
			border: thin solid black;
		}
	</style>
		
	</body>
</html>

Then define a center to begin from. As Dawkins describes, shells start small and grow at the margins. To model this with a spiral, we will draw starting from a center, and "spiral" outwards by an increasing distance.

const centerX = 200;
const centerY = 200;
let distance = 1;
for (let angle = 0; angle < 5 * 360; angle++) {
	let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
	let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
	ctx.fillRect(x, y, 1, 1);
	distance += 0.1;
}

This code is easier to write with degrees, but Math.sin and Math.cos expect radians, so we need to convert the angles.

Math.toRadians = function (number) {
	return Math.PI * number / 180;
}

This results in a somewhat blurry shell.

blurry archimedes

I fix this by drawing a line between each point.

ctx.beginPath();
ctx.moveTo(centerX, centerY);

for (let angle = 0; angle < 5 * 360; angle++) {
	left x = centerX + Math.sin(Math.toRadians(angle)) * distance;
	left y = centerY + Math.cos(Math.toRadians(angle)) * distance;
	ctx.lineTo(x, y);
	distance += 0.1;
}

ctx.stroke();

This results in a clearer spiral.

clear archimedes

Logarithmic Spiral

Unlike an archimedes spiral, a logarithmic spiral does not grow by a constant amount every iteration.

Instead they "open out" at a constant rate. For example, the gap between turns might double at every coil. The expansion rate of the spiral is actually one of the variables/dimenions used to model the different kinds of shells - Dawkins calls this "flare".

Logarithmic spirals are also known as equiangular spirals.

Often different types of shell are better modelled with logarithmic spirals.

Let's begin by copying some of the setup code from the archimedes spiral, in equiangular.html.

<!DOCTYPE html>
<html>
	<head>
		<title>Logarithmic Spiral</title>
	</head>
	<body>
	<canvas width="720" height="480" id="canvas">

	</canvas>
	<script type="text/javascript">
		const canvas = document.getElementById("canvas");
		const ctx = canvas.getContext("2d");


		Math.toRadians = function (number) {
			return Math.PI * number / 180;
		}

		const centerX = 200;
		const centerY = 200;
		let distance = 1;

	</script>

	<style type="text/css">
		canvas {
			border: thin solid black;
		}
	</style>
		
	</body>
</html>

Like before, we will draw a point for every degree.

for (let angle = 0; angle < 12 * 360; angle++) {
	let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
	let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
	ctx.fillRect(x, y, 1, 1);
	// increase distance here
}

How much do we increase distance?

For every full turn, we want to double the distance.

We can use logarithms to determine how much to grow.

We need to multiply the distance by some number 360 times (a full turn) to cause it to double.

To put it mathematically, if y is the distance and x is the multiplier then:

yx360=2yy*x^{360} = 2y

We can cancel out the distance to obtain:

x360=2x^{360} = 2

To solve this, we can take log base 2 of two.

log(x360)=1log(x^{360}) = 1

Which, is identical to 360log(x)=1360*log(x) = 1

We can rearrange this to get log(x)=1/360log(x) = 1/360

If log(x)log(x) is 1/3601/360, then 21/360=x2^{1/360} = x.

Substituting that in our code, we get:

for (let angle = 0; angle < 12 * 360; angle++) {
	let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
	let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
	ctx.fillRect(x, y, 1, 1);
	distance *= 2**(1/360);
}

blurry logarithmic

Like before, let's reduce blurring by using lines instead.

const centerX = 200;
const centerY = 200;
let distance = 1;

ctx.beginPath();
ctx.moveTo(centerX, centerY);

for (let angle = 0; angle < 12 * 360; angle++) {
	let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
	let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
	ctx.lineTo(x, y);
	distance *= (2**(1/360));
}

ctx.stroke();

clear logarithmic

This doesn't just work for doubling, we can substitue any growth factor we like.

for (let angle = 0; angle < 12 * 360; angle++) {
	let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
	let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
	ctx.lineTo(x, y);
	distance *= 3**(1/360);
}

triple logarithmic

Real shells have tubes, not lines. To mimic this, we will draw circles instead of lines.

const ctx = canvas.getContext("2d");

ctx.drawCircle = function(x, y, radius) {
	ctx.beginPath();
	ctx.arc(x, y, radius, 0, Math.PI*2, false);
	ctx.stroke();
}
for (let angle = 0; angle < 12 * 360; angle++) {
	let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
	let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
	ctx.drawCircle(x, y, 15);
	distance *= 2**(1/360);
}

circle logarithmic

The tubes usually grow wider, we can model this by making it proportional to the distance from the centre.

for (let angle = 0; angle < 12 * 360; angle++) {
	let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
	let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
	ctx.drawCircle(x, y, distance/6);
	distance *= 2**(1/360);
}

proportional circle logarithmic

Some Cool Patterns

In a future tutorial I will demonstrate how to go from here to creating all sorts of different shell shapes, but here I'd like to demonstrate different kinds of patterns that can be obtained by playing around with a few variables.

for (let angle = 0; angle < 12 * 360; angle++) {
	let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
	let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
	ctx.drawCircle(x, y, distance%15);
	distance *= 2**(1/360);
}

modulo fifteen

for (let angle = 0; angle < 12 * 360; angle++) {
	let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
	let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
	ctx.drawCircle(x, y, Math.abs(distance%15));
	distance *= -(2**(1/360));
}

negative modulo fifteen

for (let angle = 0; angle < 12 * 360; angle++) {
	let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
	let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
	ctx.drawCircle(x, y, Math.abs(distance/6));
	distance *= -(2**(1/360));
}

negative two

let distance = 2;

for (let angle = 0; angle < 12 * 360; angle++) {
	let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
	let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
	ctx.drawCircle(x, y, Math.abs(distance/6));
	distance *= (1.5**(1/360));
}

one point five

let distance = 2;

for (let angle = 0; angle < 12 * 360; angle++) {
	let x = centerX + Math.sin(Math.toRadians(angle)) * distance;
	let y = centerY + Math.cos(Math.toRadians(angle)) * distance;
	ctx.drawCircle(x, y, Math.abs(distance/6));
	distance *= -(1.5**(1/360));
}

negative one point five

PreviousNext

Subscribe to my blog



© 2023